Entity Framework DateTime Time Zone Issues and Solutions
TLDR
- If the
Kindproperty of aDateTimeisUnspecified, unexpected offsets occur during time zone conversions. - Database columns (such as
datetime2) do not store time zone information, causing Entity Framework to default theKindtoUnspecifiedwhen retrieving data, which leads to incorrect time display on the frontend. - The solution is to use a
ValueConverterto ensure the time is UTC before writing to the database and to force theKindtoUtcupon retrieval. - It is recommended to use
DbContext.ConfigureConventions()for unified configuration to avoid manually defining converters for each property.
DateTime Time Zone Format Issues
In .NET, the Kind property of a DateTime determines how time is handled. When Kind is Unspecified, calling ToLocalTime() or ToUniversalTime() causes the system to perform incorrect time zone offset calculations based on the current environment.
When this problem occurs: When developers use DateTime objects directly without explicitly specifying or converting their Kind property, causing the system to misjudge the time zone during time operations.
The impact of Kind on conversion behavior:
Local: CallingToLocalTime()does not change the time.Utc: CallingToUniversalTime()does not change the time.Unspecified: CallingToLocalTime()assumes the original time is UTC and adds the time zone offset; callingToUniversalTime()assumes the original time is local time and subtracts the offset.
TIP
To avoid such issues, you can refer to the IClock implementation in the ABP.IO framework, which performs normalization by comparing the Kind.
Entity Framework DateTime Time Zone Issues
When using database types that do not contain time zone information (such as datetime or datetime2), the Kind of data retrieved by Entity Framework is always Unspecified. This causes the time string to lack the Z suffix representing UTC when serialized to the frontend, resulting in an 8-hour discrepancy between the displayed time and the expected time.
When this problem occurs: When a project uses Code First or reverse engineering, and the database columns do not store time zone information, causing the Kind property of the object to fail to correctly reflect the UTC status after EF Core reads the data.
Solution: Using ValueConverter
Through ValueConverter, you can force a conversion to UTC when writing data and force the Kind to be marked as Utc when reading.
1. Define the Converter
public class UtcDateTimeValueConverter : ValueConverter<DateTime, DateTime> {
public UtcDateTimeValueConverter()
: base(v => ToDb(v), v => FromDb(v)) {
}
private static DateTime ToDb(DateTime dateTime) {
return dateTime.Kind == DateTimeKind.Utc ? dateTime : dateTime.ToUniversalTime();
}
private static DateTime FromDb(DateTime dateTime) {
return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
}
}2. Global Configuration (Recommended)
In .NET 6 and later versions, it is recommended to use ConfigureConventions to handle all DateTime columns uniformly, without needing to set them one by one:
public partial class MyDbContext : DbContext {
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) {
ArgumentNullException.ThrowIfNull(configurationBuilder);
configurationBuilder.Properties<DateTime>().HaveConversion<UtcDateTimeValueConverter>();
}
}If using a DbContext generated by reverse engineering, you can use the OnModelCreatingPartial method to extend it:
partial void OnModelCreatingPartial(ModelBuilder modelBuilder) {
foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) {
foreach (IMutableProperty property in entityType.GetProperties()) {
if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?)) {
property.SetValueConverter(typeof(UtcDateTimeValueConverter));
}
}
}
}TIP
A complete executable example for this article: CloudyWing/EfCoreBehaviorSample.
Change Log
- 2024-08-15 Initial documentation created.
- 2026-05-29 Added link to the corresponding GitHub example project.